Working with API Authentication and Environment Variables in Python
Course: Python Training
Topic: Working with API Authentication and Environment Variables
Course Link: Python Training Course at JustAcademy
Demo Link: Register for a Free Course Demo
Website: Visit JustAcademy
1. Introduction to API Authentication
APIs (Application Programming Interfaces) allow different software applications to communicate and exchange data. For example, a Python application may use a weather API to retrieve current weather information, a payment API to process transactions, or a messaging API to send notifications.
Many APIs restrict access to authorized users or applications. API authentication is the process of verifying the identity of the client making a request. Python applications commonly use API keys, bearer tokens, OAuth 2.0 access tokens, or other credentials to authenticate API requests.
Environment variables provide a way to configure applications without writing sensitive values directly into source code. They are commonly used to store API keys, access tokens, database connection details, and environment-specific settings.
Learning Objectives
- Understand API authentication and why it is necessary.
- Identify common API authentication methods.
- Send authenticated API requests using Python and the requests library.
- Read configuration values from environment variables.
- Use a .env file with the python-dotenv package during local development.
- Handle authentication failures and API errors.
- Protect credentials and avoid exposing secrets in source code or logs.
2. What Is an API?
An API defines how one application can request information or functionality from another application. A typical HTTP API receives a request, checks the request details and permissions, processes it, and returns a response.
Example of an API Request
Suppose a Python application needs information about a city from a weather service. It sends an HTTP request to the provider's API endpoint. The service checks the request and returns weather information if the request is valid and authorized.
import requests
url = "https://api.example.com/weather"
response = requests.get(url, timeout=10)
print(response.status_code)
print(response.text)
This example demonstrates a basic HTTP GET request. The example endpoint is a placeholder and will not return real weather data. An actual service may require authentication headers, query parameters, or a request body.
Common HTTP Methods
| Method |
Purpose |
Example |
| GET |
Retrieve information |
Fetch a user profile |
| POST |
Create a resource or submit data |
Create a customer |
| PUT |
Replace a resource |
Update an entire profile |
| PATCH |
Partially update a resource |
Change a user's email address |
| DELETE |
Delete a resource |
Remove a record |
3. What Is API Authentication?
API authentication verifies that a request comes from a client or identity that can be recognized by the API provider. Depending on the service, successful authentication may be followed by authorization checks to determine which resources the client is permitted to access.
Authentication and authorization are related but different concepts:
- Authentication: Who or what is making the request?
- Authorization: What is that identity allowed to do?
For example, an application might successfully authenticate using an access token but still receive a forbidden response because the token does not have permission to access a particular resource.
Why Is API Authentication Important?
- Prevents unauthorized access to protected API resources.
- Helps identify applications and users making requests.
- Supports access control and usage limits.
- Protects private information and business operations.
- Allows providers to revoke or restrict credentials when necessary.
- Can support monitoring, auditing, and abuse prevention.
4. Common API Authentication Methods
4.1 API Key Authentication
An API key is a credential issued by an API provider to identify or authorize a client. The provider specifies how the key must be supplied. Some APIs accept a custom HTTP header, while others require a query parameter.
Example using a header:
import os
import requests
api_key = os.getenv("WEATHER_API_KEY")
if not api_key:
raise RuntimeError("WEATHER_API_KEY is not configured")
url = "https://api.example.com/weather"
headers = {
"X-API-Key": api_key
}
response = requests.get(
url,
headers=headers,
timeout=10
)
print(response.status_code)
The endpoint and header name above are illustrative. Always follow the API provider's documentation. Some providers use headers such as api-key or X-API-Key, while others use a different authentication scheme.
Advantages: API keys are relatively simple to configure and use.
Limitations: A long-lived key may provide broad access if the provider does not restrict its permissions. Keys can be copied or stolen if exposed.
4.2 Bearer Token Authentication
A bearer token is a credential presented to an API, usually in the HTTP Authorization header. A bearer token grants access to whoever possesses it, so it must be protected like a password.
import os
import requests
token = os.getenv("API_ACCESS_TOKEN")
if not token:
raise RuntimeError("API_ACCESS_TOKEN is not configured")
headers = {
"Authorization": f"Bearer {token}"
}
response = requests.get(
"https://api.example.com/profile",
headers=headers,
timeout=10
)
print(response.status_code)
Bearer tokens are often time-limited. An application may need to obtain a new token when the current token expires. The correct token acquisition and refresh procedure depends on the API provider.
4.3 Basic Authentication
HTTP Basic Authentication sends a username and password encoded using Base64. Base64 is an encoding format, not encryption, so Basic Authentication should only be used over HTTPS. Avoid it when a provider offers a more appropriate token-based mechanism.
import os
import requests
from requests.auth import HTTPBasicAuth
username = os.getenv("API_USERNAME")
password = os.getenv("API_PASSWORD")
if not username or not password:
raise RuntimeError("API credentials are not configured")
response = requests.get(
"https://api.example.com/account",
auth=HTTPBasicAuth(username, password),
timeout=10
)
print(response.status_code)
Use Basic Authentication only when the API explicitly supports it. Do not assume that every endpoint accepts a username and password.
4.4 OAuth 2.0
OAuth 2.0 is an authorization framework that allows applications to obtain limited access to protected resources. It commonly uses access tokens and may involve refresh tokens. Depending on the application, an OAuth flow may require a user to sign in and approve permissions.
Common OAuth 2.0 scenarios include:
- Authorization Code with PKCE: Commonly used by applications acting on behalf of a user, including public clients.
- Client Credentials: Used by confidential server-side applications to access resources under their own identity when the provider supports this flow.
- Refresh Token: Used in supported flows to obtain a new access token without repeating the full authorization process.
OAuth 2.0 configuration is provider-specific. Use the provider's official documentation, requested scopes, approved redirect URIs, and recommended client libraries. Do not place client secrets or refresh tokens in browser code or public mobile application bundles.
4.5 Authentication Methods Compared
| Method |
Credential |
Typical Use |
Important Consideration |
| API Key |
Key issued by a provider |
Simple service access |
Restrict, rotate, and protect the key |
| Bearer Token |
Access token |
Protected API endpoints |
Protect the token and handle expiration |
| Basic Authentication |
Username and password |
Services that explicitly support Basic Auth |
Use HTTPS and avoid unnecessary password exposure |
| OAuth 2.0 |
Access token, sometimes refresh token |
Delegated or application-level authorization |
Use the correct flow, scopes, and secure token storage |
5. Understanding Environment Variables
An environment variable is a named value provided to a running process by its operating environment. Applications use environment variables to access configuration without embedding every value in the program's source code.
For example, a Python application might read these environment variables:
WEATHER_API_KEY=your_api_key_here
API_BASE_URL=https://api.example.com
APP_ENV=development
REQUEST_TIMEOUT=10
The values shown are examples. Replace them with the settings appropriate for your application. Never commit real credentials to a public repository.
Benefits of Environment Variables
- Improved secret handling: Credentials can be supplied separately from source code.
- Environment-specific configuration: Development, testing, and production can use different values.
- Portability: The same code can run in different environments with different configuration.
- Easier deployment: Hosting platforms and container systems can inject configuration when starting the application.
- Reduced accidental exposure: Credentials do not need to appear directly in committed Python files.
Environment variables improve configuration management, but they are not a complete secret-management system. Values can still leak through process inspection, debug output, logs, deployment configuration, or a compromised machine. Production applications may benefit from a dedicated secrets manager.
6. Reading Environment Variables with Python
Python provides the built-in os module to access environment variables.
6.1 Using os.getenv()
The os.getenv() function retrieves an environment variable. If the variable does not exist, it returns None unless a default value is supplied.
import os
api_key = os.getenv("WEATHER_API_KEY")
if api_key:
print("API key is configured")
else:
print("API key is missing")
This code checks whether the variable exists without printing the actual credential. Avoid printing API keys or tokens in terminal output, logs, screenshots, or error messages.
6.2 Using a Default Value
import os
api_base_url = os.getenv(
"API_BASE_URL",
"https://api.example.com"
)
print(api_base_url)
The second argument supplies a fallback when the environment variable is not set. Defaults are appropriate for non-sensitive settings, such as a development endpoint. Do not use a fake or empty default for a credential that is required to access a protected API.
6.3 Using os.environ
The os.environ mapping provides access to the current process environment. Accessing a missing key directly raises KeyError.
import os
try:
api_key = os.environ["WEATHER_API_KEY"]
except KeyError:
raise RuntimeError(
"Required environment variable WEATHER_API_KEY is missing"
)
print("Configuration loaded successfully")
Both os.getenv() and os.environ are useful. Choose the approach that makes missing configuration easiest to handle clearly.
7. Setting Environment Variables
7.1 Setting a Variable in Windows Command Prompt
set WEATHER_API_KEY=your_api_key_here
python app.py
This sets the variable for commands running in that Command Prompt session. Use a separate terminal session if you need a different environment.
7.2 Setting a Variable in Windows PowerShell
$env:WEATHER_API_KEY = "your_api_key_here"
python app.py
7.3 Setting a Variable in macOS or Linux
export WEATHER_API_KEY="your_api_key_here"
python app.py
Shell environment variables are typically inherited by child processes. Their persistence depends on how and where they are configured. These examples are suitable for local practice, but production credentials should be managed through the deployment environment or an approved secrets manager.
8. Using a .env File in Python
A .env file is a convenient way to store local development configuration as key-value pairs. Python does not automatically load a .env file into the process environment. A package such as python-dotenv can load it.
8.1 Install Required Packages
python -m pip install requests python-dotenv
The requests package sends HTTP requests, while python-dotenv loads values from a .env file into the environment.
8.2 Create a .env File
WEATHER_API_KEY=replace_with_your_real_key
API_BASE_URL=https://api.example.com
REQUEST_TIMEOUT=10
Save this file in your project's working directory. Use real credentials only in your private local environment. The API base URL shown here is illustrative.
8.3 Load the .env File
import os
from dotenv import load_dotenv
load_dotenv()
api_key = os.getenv("WEATHER_API_KEY")
api_base_url = os.getenv(
"API_BASE_URL",
"https://api.example.com"
)
if not api_key:
raise RuntimeError(
"WEATHER_API_KEY is missing from the environment"
)
print("Configuration loaded successfully")
print("API base URL:", api_base_url)
By default, load_dotenv() looks for a .env file and loads its values into the process environment. Existing environment variables generally take precedence unless the override behavior is explicitly changed. In production, deployment-provided environment variables or a secrets manager are usually preferable to relying on a local .env file.
8.4 Add .env to .gitignore
Use a .gitignore file to prevent local secrets from being accidentally committed to Git.
# Local environment configuration
.env
.env.*
# Allow a template containing placeholders
!.env.example
# Python-generated files
__pycache__/
*.py[cod]
.venv/
Review ignore rules carefully. If a secret was already committed, adding the file to .gitignore does not remove the secret from Git history or revoke the credential. Revoke or rotate the exposed credential and follow the repository's remediation process.
8.5 Create a Safe .env.example File
A sample environment file documents the configuration required to run the project without including actual secrets.
WEATHER_API_KEY=replace_with_your_api_key
API_BASE_URL=https://api.example.com
REQUEST_TIMEOUT=10
Commit this placeholder file when useful. Team members can copy it to a local .env file and enter their own credentials.
9. Combining API Authentication and Environment Variables
A common pattern is to load credentials from the environment and then add them to the HTTP request according to the API provider's authentication requirements.
Complete Example Using a Bearer Token
import os
import requests
from dotenv import load_dotenv
load_dotenv()
API_BASE_URL = os.getenv("API_BASE_URL")
API_ACCESS_TOKEN = os.getenv("API_ACCESS_TOKEN")
REQUEST_TIMEOUT = 10
if not API_BASE_URL:
raise RuntimeError("API_BASE_URL is not configured")
if not API_ACCESS_TOKEN:
raise RuntimeError("API_ACCESS_TOKEN is not configured")
headers = {
"Authorization": f"Bearer {API_ACCESS_TOKEN}",
"Accept": "application/json"
}
try:
response = requests.get(
f"{API_BASE_URL.rstrip('/')}/profile",
headers=headers,
timeout=REQUEST_TIMEOUT
)
response.raise_for_status()
data = response.json()
print("API request successful")
print(data)
except requests.exceptions.Timeout:
print("The API request timed out.")
except requests.exceptions.HTTPError as exc:
status = exc.response.status_code
if status == 401:
print("Authentication failed. Check the access token.")
elif status == 403:
print("The token may not have the required permissions.")
else:
print(f"HTTP error: {status}")
except requests.exceptions.RequestException:
print("A network or request error occurred.")
except ValueError:
print("The API response was not valid JSON.")
This example assumes that the API supports bearer tokens and provides a /profile endpoint. The base URL and endpoint must be changed to match the actual provider. The exception handling deliberately avoids printing the token or the complete request headers.
How the Code Works
- Load configuration:
load_dotenv() loads local development variables.
- Read credentials:
os.getenv() retrieves the base URL and access token.
- Validate configuration: The application stops early if required values are missing.
- Build the request: The token is included in the Authorization header.
- Send the request:
requests.get() sends the HTTP request with a timeout.
- Check the response:
raise_for_status() raises an HTTP error for unsuccessful status codes.
- Process the data: The application parses the response as JSON.
- Handle errors: Specific exceptions are handled without exposing sensitive credentials.
10. Understanding API Response Status Codes
HTTP status codes help developers identify the result of an API request. The exact meaning and response behavior can vary by service.
| Status Code |
Meaning |
Possible Action |
| 200 OK |
Request succeeded |
Process the returned data |
| 201 Created |
A resource was created |
Record the new resource details |
| 400 Bad Request |
Request is invalid |
Review parameters and request body |
| 401 Unauthorized |
Authentication is missing or invalid |
Check the credential and authentication scheme |
| 403 Forbidden |
Access is not permitted |
Check permissions, scopes, and account access |
| 404 Not Found |
Resource or endpoint was not found |
Verify the URL and resource identifier |
| 408 Request Timeout |
The server timed out waiting for the request |
Review network conditions and retry policy |
| 429 Too Many Requests |
Rate limit was exceeded |
Respect rate-limit headers and retry guidance |
| 500 Internal Server Error |
Server encountered an error |
Check service status and retry safely if appropriate |
| 502 / 503 / 504 |
Gateway or service availability problem |
Check provider status and retry according to policy |
A 401 response usually indicates an authentication problem, while a 403 response usually indicates insufficient permission. However, some providers intentionally use different status codes or messages, so consult the provider's documentation.
11. Security Best Practices for API Credentials
11.1 Never Hardcode Secrets
Avoid writing credentials directly into source code.
# Insecure: do not hardcode real credentials
API_KEY = "real-secret-key"
Instead, load them from environment variables:
import os
API_KEY = os.getenv("API_KEY")
if not API_KEY:
raise RuntimeError("API_KEY is not configured")
11.2 Never Commit Real Secrets to Version Control
Keep private .env files, access tokens, passwords, and production secrets out of Git repositories. Use placeholders in documentation and sample configuration files.
11.3 Use HTTPS
HTTPS encrypts data in transit and helps prevent attackers from reading or modifying credentials sent over the network. Never send sensitive authentication data to an untrusted HTTP endpoint.
11.4 Apply the Principle of Least Privilege
Grant API credentials only the permissions needed for the task. For example, an application that only reads weather information should not receive administrative access to unrelated services.
11.5 Rotate and Revoke Credentials
Rotate keys and tokens according to the provider's policies. If a credential is leaked or suspected to be compromised, revoke or rotate it promptly. Updating a local environment variable alone does not invalidate a previously exposed credential.
11.6 Avoid Logging Sensitive Data
Do not print authentication headers, access tokens, API keys, passwords, or complete credential-bearing URLs. Log safe diagnostic details such as the endpoint category, request ID, response status, and timestamp when appropriate.
11.7 Validate and Restrict Configuration
Check required configuration values when the application starts. For production deployments, restrict who can read and change secrets and use the platform's approved secret-management mechanisms.
11.8 Handle Token Expiration
Access tokens may expire. Use the provider's documented refresh mechanism when available. Do not repeatedly retry a request with the same expired token indefinitely, and never attempt to bypass authentication failures.
11.9 Avoid Exposing Secrets in URLs
Whenever the API supports it, send credentials through the documented authorization header instead of query parameters. URLs can be recorded in browser history, proxy logs, analytics systems, or server logs. If a provider requires a query parameter, take extra care to prevent URL logging and follow its security guidance.
12. Using Configuration for Different Environments
Applications commonly run in development, testing, staging, and production environments. Each environment may use a different API endpoint, database, or credential.
| Environment |
Purpose |
Configuration Example |
| Development |
Local coding and debugging |
Local .env file with test credentials |
| Testing |
Automated tests and validation |
Mock API or dedicated test credentials |
| Staging |
Pre-production verification |
Staging endpoint and restricted credentials |
| Production |
Real application usage |
Deployment-managed secrets and production endpoint |
Example of selecting a non-sensitive setting from the environment:
import os
app_env = os.getenv("APP_ENV", "development").lower()
if app_env not in {"development", "testing", "staging", "production"}:
raise RuntimeError("Unsupported APP_ENV value")
print("Application environment:", app_env)
Environment-specific configuration should be explicit. Avoid accidentally pointing a production application to a development service or using production credentials in automated tests.
13. Creating a Reusable API Client
As an application grows, placing authentication and request configuration in one reusable client can reduce duplicated code. The following example uses a bearer token and centralizes the base URL and timeout.
import os
import requests
from dotenv import load_dotenv
load_dotenv()
class APIClient:
def __init__(self):
self.base_url = os.getenv("API_BASE_URL")
self.token = os.getenv("API_ACCESS_TOKEN")
if not self.base_url:
raise RuntimeError("API_BASE_URL is missing")
if not self.token:
raise RuntimeError("API_ACCESS_TOKEN is missing")
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {self.token}",
"Accept": "application/json"
})
def get(self, path):
url = f"{self.base_url.rstrip('/')}/{path.lstrip('/')}"
response = self.session.get(url, timeout=10)
response.raise_for_status()
return response.json()
def close(self):
self.session.close()
if __name__ == "__main__":
client = APIClient()
try:
profile = client.get("profile")
print("Profile retrieved successfully")
print(profile)
except requests.exceptions.RequestException:
print("The API request failed.")
finally:
client.close()
This is a simple teaching example. A production client may also need connection retries for safe operations, rate-limit handling, token refresh, structured logging with secret redaction, response validation, and tests. Do not automatically retry operations that could create duplicate transactions unless the API supports safe retries or idempotency keys.
14. Common Errors and Troubleshooting
| Problem |
Likely Cause |
Solution |
| Environment variable returns None |
Variable is missing or .env was not loaded |
Check the variable name and load_dotenv() call |
| 401 Unauthorized |
Invalid, expired, or missing credential |
Verify the authentication method and credential validity |
| 403 Forbidden |
Insufficient permissions or scopes |
Check the account's permissions and token scopes |
| ModuleNotFoundError |
Dependency is not installed in the active environment |
Install the package in the correct virtual environment |
| Connection timeout |
Slow server or network connectivity problem |
Set a suitable timeout and investigate network conditions |
| JSON parsing error |
Response is empty or not JSON |
Check status code and response content type before parsing |
| Credential exposed in Git |
Secret was committed or included in a log |
Revoke or rotate the secret and remediate the exposure |
| 429 Too Many Requests |
Rate limit exceeded |
Follow Retry-After or provider-specific retry instructions |
Safe Configuration Debugging
import os
required_variables = [
"API_BASE_URL",
"API_ACCESS_TOKEN"
]
for variable in required_variables:
if os.getenv(variable):
print(f"{variable}: configured")
else:
print(f"{variable}: missing")
This diagnostic checks whether variables are present without revealing their values. Use this approach instead of printing all environment variables, which may expose unrelated secrets.
15. Testing Code That Uses API Authentication
Tests should verify how the application behaves when credentials are present, missing, invalid, or rejected by the server. Avoid depending on a live paid API for every unit test. Mock HTTP requests or use a provider's approved sandbox where appropriate.
Example: Testing a Missing API Key
import os
import unittest
from unittest.mock import patch
def get_api_key():
key = os.getenv("WEATHER_API_KEY")
if not key:
raise RuntimeError("WEATHER_API_KEY is missing")
return key
class TestAPIConfiguration(unittest.TestCase):
@patch.dict(os.environ, {}, clear=True)
def test_missing_api_key(self):
with self.assertRaises(RuntimeError):
get_api_key()
@patch.dict(
os.environ,
{"WEATHER_API_KEY": "test-placeholder"},
clear=True
)
def test_api_key_is_read(self):
self.assertEqual(
get_api_key(),
"test-placeholder"
)
if __name__ == "__main__":
unittest.main()
Save this code in a Python file and run it with python filename.py. The test uses a placeholder credential and does not contact an external API.
Testing Checklist
- Verify behavior when required environment variables are missing.
- Confirm that valid configuration is loaded correctly.
- Test 401 and 403 responses.
- Test timeout, connection failure, and invalid JSON handling.
- Confirm that logs do not include credentials.
- Mock external API calls in unit tests where practical.
16. Practical Project: Authenticated API Data Fetcher
Project Goal: Build a small Python application that reads a bearer token and base URL from environment variables, makes an authenticated request, and handles common errors safely.
Project Structure
api_auth_project/
├── app.py
├── .env
├── .env.example
├── .gitignore
└── requirements.txt
Step 1: Create requirements.txt
requests
python-dotenv
Step 2: Create .env
API_BASE_URL=https://api.example.com
API_ACCESS_TOKEN=replace_with_your_token
Keep this file private and excluded from version control. The example endpoint is a placeholder; use an actual API endpoint that you are authorized to access.
Step 3: Create app.py
import os
import requests
from dotenv import load_dotenv
load_dotenv()
def fetch_profile():
base_url = os.getenv("API_BASE_URL")
token = os.getenv("API_ACCESS_TOKEN")
if not base_url:
raise RuntimeError("API_BASE_URL is missing")
if not token:
raise RuntimeError("API_ACCESS_TOKEN is missing")
url = f"{base_url.rstrip('/')}/profile"
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/json"
}
response = requests.get(
url,
headers=headers,
timeout=10
)
response.raise_for_status()
return response.json()
def main():
try:
profile = fetch_profile()
print("Profile retrieved successfully")
print(profile)
except requests.exceptions.Timeout:
print("The request timed out.")
except requests.exceptions.HTTPError as exc:
status = exc.response.status_code
if status == 401:
print("Authentication failed.")
elif status == 403:
print("Access is forbidden.")
else:
print(f"HTTP error: {status}")
except requests.exceptions.RequestException:
print("Unable to communicate with the API.")
except RuntimeError as exc:
print(exc)
except ValueError:
print("The response could not be decoded as JSON.")
if __name__ == "__main__":
main()
Step 4: Install and Run
python -m pip install -r requirements.txt
python app.py
Expected Learning Outcomes
- Load credentials from environment variables.
- Send a bearer token in the Authorization header.
- Use a request timeout to avoid waiting indefinitely.
- Handle common HTTP and network errors.
- Keep credentials out of the source code and diagnostic messages.
Project Extensions
- Connect to a public or sandbox API that you are authorized to use.
- Read a configurable endpoint path from an environment variable.
- Add pagination for APIs that return multiple pages of results.
- Implement provider-approved token refresh behavior if required.
- Add unit tests using mocked HTTP responses.
- Write safe structured logs that include status codes but never tokens.
17. Best Practices Summary
- Use the authentication mechanism specified by the API provider.
- Read credentials from environment variables or an approved secrets manager.
- Use .env files for local development and add them to .gitignore.
- Keep a .env.example file containing placeholders, not real credentials.
- Send requests over HTTPS.
- Use least-privilege API credentials and rotate them when required.
- Validate required configuration during application startup.
- Set request timeouts and handle HTTP errors carefully.
- Never log or print API keys, passwords, access tokens, or secret-bearing headers.
- Respect API rate limits and retry guidance.
- Use sandbox services or mocks for automated testing.
- Revoke exposed credentials immediately and investigate the exposure.
18. Practice Exercises
- Write a Python program that reads
API_KEY using os.getenv().
- Create a .env file and load it with
python-dotenv.
- Send a GET request with an API key in the header specified by a test API.
- Modify the request to use bearer-token authentication.
- Handle missing environment variables without displaying any secret values.
- Write separate handlers for timeouts, HTTP errors, and connection failures.
- Create a .gitignore file that excludes .env and virtual environment files.
- Write a unit test for missing credentials.
- Explain the difference between authentication and authorization.
- Describe what steps you would take if an API token were accidentally committed to a public repository.
19. Interview Questions and Answers
Q1. What is API authentication?
Answer: API authentication is the process of verifying the identity of a client or user attempting to access an API. Common methods include API keys, bearer tokens, Basic Authentication, and OAuth-based flows.
Q2. What is the difference between authentication and authorization?
Answer: Authentication determines who or what is making a request. Authorization determines which resources or actions that identity is permitted to access.
Q3. What is an API key?
Answer: An API key is a credential issued by an API provider to identify or authorize a client. It must be protected and used according to the provider's requirements.
Q4. What is a bearer token?
Answer: A bearer token is a credential sent to an API, usually through the Authorization header. Anyone who obtains the token may be able to use its granted permissions, so it must be kept secret.
Q5. What are environment variables?
Answer: Environment variables are named values supplied to a process by its environment. Applications use them to configure behavior and access values such as API credentials without hardcoding them in source files.
Q6. How do you read an environment variable in Python?
Answer: Import the os module and use os.getenv("VARIABLE_NAME") or access os.environ["VARIABLE_NAME"]. The application should handle missing required values.
Q7. What is the purpose of python-dotenv?
Answer: The python-dotenv package loads key-value pairs from a .env file into the process environment, making it useful for local development configuration.
Q8. Why should .env files not be committed to Git?
Answer: They may contain private credentials. Committing them can expose those credentials to other users or the public. Add .env to .gitignore and use placeholder values in example files.
Q9. What is the difference between HTTP 401 and HTTP 403?
Answer: A 401 response commonly indicates missing or invalid authentication. A 403 response commonly indicates that the request is not permitted. The exact behavior can depend on the API provider.
Q10. Why is HTTPS important for API authentication?
Answer: HTTPS encrypts data exchanged between the client and server, helping protect credentials and API responses while they travel across the network.
Q11. What is OAuth 2.0?
Answer: OAuth 2.0 is an authorization framework that enables applications to obtain limited access to protected resources using access tokens and supported authorization flows.
Q12. How should an application handle expired tokens?
Answer: It should follow the provider's documented expiration and refresh process. If refreshing is unsupported or fails, it should stop the protected operation and report an authentication problem without exposing the token.
Q13. Why should API requests have timeouts?
Answer: Timeouts prevent an application from waiting indefinitely for a slow or unreachable service and allow it to recover or report the failure in a controlled manner.
Q14. How should an exposed API key be handled?
Answer: Revoke or rotate the key immediately, investigate where it was exposed, remove it from active code and configuration, and follow the provider's and organization's incident-response procedures.
Q15. How can API authentication code be tested safely?
Answer: Use mocked HTTP responses, test credentials, or an approved sandbox. Test missing credentials, authentication failures, permission errors, timeouts, and secret redaction without exposing real production tokens.
20. Quick Revision Checklist
- API authentication verifies a client's identity or credentials.
- Authorization determines what an authenticated identity may access.
- API keys and bearer tokens must be protected like secrets.
- Python's
os.getenv() reads environment variables.
python-dotenv can load a local .env file for development.
- Keep real credentials out of source code, Git, and logs.
- Use HTTPS and the API provider's documented authentication scheme.
- Use request timeouts and handle status codes and exceptions.
- Use least privilege, credential rotation, and secure deployment configuration.
- Test authentication logic with mocks or authorized test environments.
21. Conclusion
Working with API authentication and environment variables is an essential skill for building secure Python applications. Authentication mechanisms such as API keys, bearer tokens, and OAuth 2.0 help protect API resources, while environment variables make it easier to separate configuration and credentials from application code.
By combining secure credential handling, request timeouts, error management, testing, and careful deployment practices, developers can build Python applications that communicate with APIs more reliably and safely.
22. Further Learning Resources